    //  
    //  Copyright (C) 2009 
    // 
    //  This program is free software: you can redistribute it and/or modify
    //  it under the terms of the GNU General Public License as published by
    //  the Free Software Foundation, either version 3 of the License, or
    //  (at your option) any later version.
    // 
    //  This program is distributed in the hope that it will be useful,
    //  but WITHOUT ANY WARRANTY; without even the implied warranty of
    //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    //  GNU General Public License for more details.
    // 
    //  You should have received a copy of the GNU General Public License
    //  along with this program.  If not, see <http://www.gnu.org/licenses/>.
// 

using System;
using System.IO;
using System.Data;
using System.Threading;
using System.Collections.Generic;

using Banshee;
using Banshee.Base;
using Banshee.Library;
using Banshee.Sources;
using Banshee.ServiceStack;
using Banshee.Collection.Database;

using Hyena;
using Hyena.Data.Sqlite;

namespace Banshee.Plugins.LibraryWatcher
{	
    public class SourceWatcher
    {
        private LibraryImportManager import_manager;
        
        private bool forceUpdate;
        private string dir_to_watch;
        
        /*sources to refresh when something change*/
        public MusicLibrarySource music_library;
        public VideoLibrarySource video_library;
        
        private FileSystemWatcher watcher = new FileSystemWatcher ();
        		
        private List<string> tracks_to_remove = new List<string> (); /*songs to remove list*/
        private List<string> tracks_to_add = new List<string> ();    /*songs to add list*/
        
        private Thread watchThread;
        
        private bool stopThread = false;
        
        public SourceWatcher ()
        {
            import_manager = ServiceManager.Get<LibraryImportManager> ();
            import_manager.KeepUserJobHidden = true;
        }
        
        public MusicLibrarySource MusicLibrary {
            get {
                return music_library;
            }
            set {
                dir_to_watch = value.BaseDirectory;
                music_library = value;
            }
        }
        
        public VideoLibrarySource VideoLibrary {
            get {
                return video_library;
            }
            set {
                video_library = value;
            }
        }
        
        #region Public Methods
        public void StartWatch ()
        {	
            watcher.Path = dir_to_watch; 
            watcher.Created += new FileSystemEventHandler (OnFileSystemEvent);
            watcher.Deleted += new FileSystemEventHandler (OnFileSystemEvent);
            watcher.Renamed += new RenamedEventHandler (OnRenamed);
        	
            watcher.IncludeSubdirectories = true;
            watcher.EnableRaisingEvents = true;
        	
            /*the thread that make the job*/
            stopThread = false;
            watchThread = new Thread (new ThreadStart (Watcher));
            watchThread.Start ();
        	
            Log.Debug ("watching dir " + dir_to_watch);
        }
        
        public void StopWatch ()
        {
        	watcher.Created -= new FileSystemEventHandler (OnFileSystemEvent);
        	watcher.Deleted -= new FileSystemEventHandler (OnFileSystemEvent);
        	watcher.Renamed -= new RenamedEventHandler (OnRenamed);
        	watcher.EnableRaisingEvents = false;

        	stopThread = true;
        }
        #endregion
        
        #region Private Methods
        private void OnRenamed (object source, RenamedEventArgs e)
        {
            forceUpdate = true;
        }
        
        private void OnFileSystemEvent (object source, FileSystemEventArgs e)
        {
            if (e.ChangeType == WatcherChangeTypes.Created) {
                lock (tracks_to_add) {
                    tracks_to_add.Add (e.FullPath);
                }
            } else if (e.ChangeType == WatcherChangeTypes.Deleted) {
                lock (tracks_to_remove) {
                    tracks_to_remove.Add (e.FullPath);
                }
            }
        }
        
        private void Watcher ()
        {
            /*update the library at startup*/
            forceUpdate = true;

            /*the main thread*/
            while (!stopThread) {
                bool needReload = false;
                if (forceUpdate) {
                    SearchForDeletedSongs ();
                    SearchForNewSongs ();
                    forceUpdate = false;
                }
		
        		if (tracks_to_remove.Count > 0) {
                    lock (tracks_to_remove) {
                        RemoveFromLibrary (tracks_to_remove);
                    }
                    needReload = true;
                }

                if (tracks_to_add.Count > 0) {
                    lock (tracks_to_add) {
                        AddToLibrary (tracks_to_add);
                    }
                    needReload = true;
                }
 
                if (needReload) {
                    this.MusicLibrary.Reload ();
                    this.VideoLibrary.Reload ();
                    this.MusicLibrary.NotifyTracksChanged ();
                    this.VideoLibrary.NotifyTracksChanged ();
                }
                Thread.Sleep (400);
            }
        }
 
        private void AddToLibrary (List<string> tracks)
        {
            foreach (String track in tracks) {
                import_manager.ImportTrack (track);
            }
            tracks.Clear ();
        }
        
        private void RemoveFromLibrary (List<string> tracks) {
            foreach (String track in tracks) {
                RemoveTrack (track);
            }
            tracks.Clear ();
        }
        
        private void RemoveTrack (string track)
        {
            int track_id = DatabaseTrackInfo.GetTrackIdForUri (new SafeUri (track).AbsoluteUri);
            HyenaSqliteCommand delete_command;
            if (track_id > 0) {
                delete_command = new HyenaSqliteCommand (string.Format ("BEGIN;DELETE FROM CoreTracks WHERE TrackID = '{0}';COMMIT ",track_id));
            } else {
                /*if track_id is 0 you removed a directory*/
                delete_command = new HyenaSqliteCommand (string.Format ("BEGIN;DELETE FROM CoreTracks WHERE TrackID IN (SELECT TrackID FROM CoreTracks where lower(Uri) LIKE '{0}%');COMMIT",new SafeUri(track).AbsoluteUri + "/"));
            }
            ServiceManager.DbConnection.Execute (@delete_command);
        }
        
        private void SearchForDeletedSongs ()
        {	
            SafeUri  uri;
            HyenaSqliteCommand select_command = new HyenaSqliteCommand ("SELECT Uri from CoreTracks");
            IDataReader reader = ServiceManager.DbConnection.Query (select_command);
            while (reader.Read ()) {				
                string uri_str = (string)reader["Uri"];
                if (uri_str.StartsWith ("file:///") || uri_str.Contains (dir_to_watch)) {
                    uri = new SafeUri (uri_str);
            } else {
                    uri = new SafeUri (dir_to_watch + "/" + uri_str); 
                }
                if (!Banshee.IO.File.Exists (uri)) {
                    tracks_to_remove.Add (uri.AbsolutePath);
                }
            }
        }
 
        private void SearchForNewSongs ()
        {
            string[] filenames = System.IO.Directory.GetFiles (dir_to_watch,"*", SearchOption.AllDirectories);
            foreach (string filename in filenames) {
                if (IsValidPath (filename)) {
                    tracks_to_add.Add (filename);	
                }
            }
        }
 
        private bool IsValidPath (string track_path)
        {
            if (System.IO.Directory.Exists (track_path)) {
                return false;
            }
            if (!DatabaseImportManager.IsWhiteListedFile (track_path)) {
                return false;
            }
            int[] source_ids =  new int [] { this.MusicLibrary.DbId, this.VideoLibrary.DbId };
            if (DatabaseTrackInfo.ContainsUri (new SafeUri(track_path),source_ids)) {
                return false;
            }
            return true;
        }
        #endregion
    }
}
